
#include <vector>
#include <apr_date.h>
#include "../crypt/RSAPublicKey.h"
#include "../crypt/RSAPrivateKey.h"
#include "../crypt/AESSymmKey.h"
#include "../iface/APRInterface.h"
#include "../utils/StringUtils.h"
#include "KeyTableView.h"
#include "NewKeyDB.h"

KeyTableView::KeyTableView(NewKeyDB *newkdb, int ikeytableusetype,
   const char *itablename, const char *itableWIPpwd,
   const char *iresidentkeyname, const char *iresidentkeypwd)
{
   keytableusetype = ikeytableusetype;
   tablename = (itablename) ? strdup(itablename) : NULL;
   tableWIPpwd = (itableWIPpwd) ? strdup(itableWIPpwd) : NULL;
   residentkeyname = (iresidentkeyname) ? strdup(iresidentkeyname) : NULL;
   ownkey = NULL;
   int errval = TryToGetKeyFromTable(iresidentkeyname, iresidentkeypwd, &ownkey);
   // If the key is actually not found, then the keys could be generated
   couldgeneratekeysflag = (errval == KEY_OPEN_ERROR__KEY_NOT_FOUND);
   TryToGetKeyErrorCheck(iresidentkeyname, errval);
}

KeyTableView::~KeyTableView()
{
   if (tablename) free(tablename);
   tablename = NULL;
   if (tableWIPpwd) free(tableWIPpwd);
   tableWIPpwd = NULL;
   if (residentkeyname) free(residentkeyname);
   residentkeyname = NULL;
   if (ownkey) delete ownkey;
   ownkey = NULL;
}

int KeyTableView::CountKeyChainOfOwner(const char *owner)
{
   int keycount = 0;
   nkeydb->SQLGetSingleIntf(&keycount,
      "select count(keyname) from '%s' where owner='%s';", tablename, owner);
   return keycount;
}

char *KeyTableView::GetLatestKeyNameOfOwner(const char *owner)
{
   char *result = NULL;
   nkeydb->SQLGetSingleStringf(&result,
      "select keyname from '%s' where owner='%s' and keymark='%c';", tablename, owner, KEYMARK_CHAINLATEST);
   if (result) return result;
   nkeydb->SQLGetSingleStringf(&result,
      "select keyname from '%s' where owner='%s' and keymark='%c';", tablename, owner, KEYMARK_CHAINROOT);
   if (result) return result;
   int chaincount = CountKeyChainOfOwner(owner);
   if (CountKeyChainOfOwner(owner) > 0)
   {
      errprint("%s: there are %d keys owned by '%s' but none are directly latest - fixme broken keymark",
         tablename, chaincount, owner);
      std::vector<char *> *keynamedate_info =
         nkeydb->SQLScanAllTextf("select keyname,keymark from '%s' where owner='%s';", tablename, owner);
      if ((keynamedate_info == NULL) || (keynamedate_info->size() == 0))
      {
         errprint("%s: could not get keys owned by '%s', possible corruption", tablename, owner);
         if (keynamedate_info) delete keynamedate_info;
         return NULL;
      }
      apr_time_t minimum = 0;
      int chosenslot = -1;
      apr_time_t timet = apr_time_now();
      for (int i = 0; i < chaincount; i++)
      {
         apr_time_t timebefore = apr_date_parse_rfc(keynamedate_info->at(i*2 + 1));
         if ((timet - timebefore) < minimum)
         {
            minimum = (timet - timebefore);
            chosenslot = i;
         }
      }
      if (chosenslot == -1)
      {
         errprint("%s: corrupted or broken dates in key chain '%s'", tablename, owner);
         StringUtils::DestroyStringList(keynamedate_info);
         return NULL;
      }
      errprint("%s: found latest key owned by '%s' dated '%s': is latest",
         tablename, owner, keynamedate_info->at(chosenslot*2 + 1));
      char *namedup = strdup(keynamedate_info->at(chosenslot*2 + 0));
      StringUtils::DestroyStringList(keynamedate_info);
      return namedup;
   }
   errprint("%s: no keys found with owner '%s'", tablename, owner);
   return NULL;
}

char KeyTableView::GetMarkOfKey(const char *keyname)
{
   char *result = NULL;
   nkeydb->SQLGetSingleStringf(&result,
      "select keymark from '%s' where keyname='%s';", tablename, keyname);
   if (result == NULL) return '\0';
   char retval = result[0];
   free(result);
   return retval;
}

void KeyTableView::SetMarkOfKey_unsafe(const char *keyname, char newmark)
{
   if ((keyname == NULL) || (strlen(keyname) == 0))
   {
      errprint("name of key given should not be %s string", (keyname) ? "an empty" : "a NULL");
      return;
   }
   nkeydb->SQLInstantExecf_unsafe("update '%s' set keymark='%c' where keyname='%s';", tablename, newmark, keyname);
}

void KeyTableView::RetryGettingOwnKeys(const char *ownpwd)
{
   if (couldgeneratekeysflag)
   {
      int errval = TryToGetKeyFromTable(residentkeyname, ownpwd, &ownkey);
      if (errval != KEY_OPEN_ERROR__KEY_NOT_FOUND)
      {
         if (errval != KEY_OPEN_ERROR__NO_ERROR)
            TryToGetKeyErrorCheck(residentkeyname, errval);
         couldgeneratekeysflag = false;
      }
   }
}

int KeyTableView::TryToInsertKeyIntoTable(const char *name, const char *pwd, char sig, const char *owner, Key *key)
{
   return TryToInsertKeyIntoTable_tsafe(name, pwd, sig, owner, key, true);
}

int KeyTableView::TryToInsertKeyIntoTable_tsafe(const char *name, const char *pwd, char sig, const char *owner, Key *key, int uselock)
{
   char timebuf[APR_RFC822_DATE_LEN + 1];
   apr_status_t status;
   apr_time_t timet = apr_time_now();
   status = apr_rfc822_date(timebuf, timet);
   CheckAPRError(status);
   int errval = KEY_INSERT_ERROR__NO_ERROR;
   if ((name == NULL) || (strlen(name) == 0)) return KEY_INSERT_ERROR__INVALID_KEY_NAME;
   char *namedup = strdup(name);
   StringUtils::AggressiveStringSanitiser(namedup);
   if (strcmp(namedup, name) != 0) { free(namedup); return KEY_INSERT_ERROR__INVALID_KEY_NAME; }
   free(namedup);
   if (key == NULL) return KEY_INSERT_ERROR__INVALID_KEY_SRC;
   int retval = 0;
   char *textdata = NULL;
   if ((pwd == NULL) || (strlen(pwd) == 0))
   {
      retval = key->ExportKeyToASCII(&textdata);
   } else
   {
      retval = key->ExportKeyToASCIIWithPassword(&textdata, pwd);
   }
   if (retval != 0) return KEY_INSERT_ERROR__EXPORT_ERROR;
   char *nowner = (owner == NULL) ? strdup(owner) : strdup(name);
   if (uselock)
   {
      nkeydb->SQLInstantExecf("insert into '%s' "
         "(keyname, keytype, keydata, owner, latest, usedrefcount, existsrefcount, rfc822date) values "
         "('%s', '%s', '%s', '%s', '%s', '%c', '0', '0', '%s');",
         tablename, name, key->GetKeyTypeAbbrName(), textdata, nowner, sig, timebuf);
   } else
   {
      nkeydb->SQLInstantExecf_unsafe("insert into '%s' "
         "(keyname, keytype, keydata, owner, latest, usedrefcount, existsrefcount, rfc822date) values "
         "('%s', '%s', '%s', '%s', '%s', '%c', '0', '0', '%s');",
         tablename, name, key->GetKeyTypeAbbrName(), textdata, nowner, sig, timebuf);
   }
   free(nowner);
   return errval;
}

int KeyTableView::TryToGetKeyFromTable(const char *name, const char *pwd, Key **key)
{
   int errval = KEY_OPEN_ERROR__NO_ERROR;
   std::vector<char *> *key_strlist =
      nkeydb->SQLScanRowTextf("select keytype,keydata from '%s' where keyname = '%s';", tablename, name);
   if ((name == NULL) || (strlen(name) == 0)) return KEY_OPEN_ERROR__INVALID_KEY_NAME;
   char *namedup = strdup(name);
   StringUtils::AggressiveStringSanitiser(namedup);
   if (strcmp(namedup, name) != 0) return KEY_OPEN_ERROR__INVALID_KEY_NAME;
   free(namedup);
   if (key == NULL) return KEY_OPEN_ERROR__INVALID_KEY_DST;
   if ((key_strlist == NULL) || (key_strlist->size() == 0))
   {
      if (key_strlist) delete key_strlist;
      return KEY_OPEN_ERROR__KEY_NOT_FOUND;
   }
   if (key_strlist->size() != 2)
   {
      StringUtils::DestroyStringList(key_strlist);
      return KEY_OPEN_ERROR__FAILED_SANITY;
   }
   if (strcmp(key_strlist->at(0), "PrivateRSA") == 0)
   {
      RSAPrivateKey *prvkey = new RSAPrivateKey(name);
      if (pwd)
         errval = prvkey->ImportKeyFromASCIIWithPassword(key_strlist->at(1), pwd, name);
      else
         errval = prvkey->ImportKeyFromASCII(key_strlist->at(1), name);
      if (errval)
      {
         delete prvkey;
         return KEY_OPEN_ERROR__KEY_IMPORT_FAILED;
      }
      *key = (Key *)prvkey;
   } else
   if (strcmp(key_strlist->at(0), "PublicRSA") == 0)
   {
      RSAPublicKey *pubkey = new RSAPublicKey(name);
      if (pwd)
         errval = pubkey->ImportKeyFromASCIIWithPassword(key_strlist->at(1), pwd, name);
      else
         errval = pubkey->ImportKeyFromASCII(key_strlist->at(1), name);
      if (errval)
      {
         delete pubkey;
         return KEY_OPEN_ERROR__KEY_IMPORT_FAILED;
      }
      *key = (Key *)pubkey;
   } else
   if (strcmp(key_strlist->at(0), "SymmAES") == 0)
   {
      AESSymmKey *symkey = new AESSymmKey(name);
      if (pwd)
         errval = symkey->ImportKeyFromASCIIWithPassword(key_strlist->at(1), pwd, name);
      else
         errval = symkey->ImportKeyFromASCII(key_strlist->at(1), name);
      if (errval)
      {
         delete symkey;
         return KEY_OPEN_ERROR__KEY_IMPORT_FAILED;
      }
      *key = (Key *)symkey;
   }
   if (*key)
      return KEY_OPEN_ERROR__NO_ERROR;
   else
      return KEY_OPEN_ERROR__KEY_NULL;
}

void KeyTableView::TryToGetKeyErrorCheck(const char *keyname, int terrno)
{
   switch (terrno)
   {
      case KEY_OPEN_ERROR__NO_ERROR:
         break;
      case KEY_OPEN_ERROR__INVALID_KEY_NAME:
         errprint("(table: %s, key: %s) key name to be found was invalid", tablename, (keyname) ? keyname : "(null)"); break;
      case KEY_OPEN_ERROR__INVALID_KEY_DST:
         errprint("(table: %s, key: %s) invalid key destination pointer", tablename, (keyname) ? keyname : "(null)"); break;
      case KEY_OPEN_ERROR__KEY_NOT_FOUND:
         errprint("(table: %s, key: %s) key not found in database", tablename, (keyname) ? keyname : "(null)"); break;
      case KEY_OPEN_ERROR__FAILED_SANITY:
         errprint("(table: %s, key: %s) key importing failed basic sanity checks", tablename, (keyname) ? keyname : "(null)"); break;
      case KEY_OPEN_ERROR__KEY_IMPORT_FAILED:
         errprint("(table: %s, key: %s) key could not be recovered from data in database", tablename, (keyname) ? keyname : "(null)"); break;
      case KEY_OPEN_ERROR__KEY_NULL:
         errprint("(table: %s, key: %s) key could not be recovered from data in database: key was NULL", tablename, (keyname) ? keyname : "(null)"); break;
      default:
         errprint("(table: %s, key: %s) unknown get key error from key database", tablename, (keyname) ? keyname : "(null)"); break;
   }
}

void KeyTableView::TryToInsertKeyErrorCheck(const char *keyname, int terrno)
{
   switch (terrno)
   {
      case KEY_INSERT_ERROR__NO_ERROR:
         break;
      case KEY_INSERT_ERROR__INVALID_KEY_NAME:
         errprint("(table: %s, key: %s) key name to be found was invalid", tablename, (keyname) ? keyname : "(null)"); break;
      case KEY_INSERT_ERROR__INVALID_KEY_SRC:
         errprint("(table: %s, key: %s) invalid key source pointer", tablename, (keyname) ? keyname : "(null)"); break;
      case KEY_INSERT_ERROR__EXPORT_ERROR:
         errprint("(table: %s, key: %s) key export to text failed", tablename, (keyname) ? keyname : "(null)"); break;
      default:
         errprint("(table: %s, key: %s) unknown insert key error from key database", tablename, (keyname) ? keyname : "(null)"); break;
   }         
}
